Chapter 7 Linking
#CSAPP
命令行工具 gcc 属于 compiler driver,内部包含了对 C 的预处理(cpp)、编译(cc1)、汇编(as)、链接(ld)步骤。
静态链接是通过一系列对象文件构建可执行程序的过程,源代码的编写、编译的解耦得益于链接。两个主要机制
- Symbol Resolution
- Relocation
对象文件(object file, object module)包括三种
- Relocatable,由编译器汇编器生成
- Executable,由链接器生成
- Shared,一种特殊的 Relocatable
object file format
- Unix, ELF, Executable and Linkable Format
- Windows, PE, Portable Executable
- Max-OS, Mach-O
Relocatable object files content in seq
- ELF header,
readelf -h
,Program header、Entry point 对 Relocatable 无意义 - sections (in seq):.text, .data, .bss, .rodata, .symtab, .rela.text, .rela.data, .debug, .line, .strtab
- section header
Symbol Resolution
relocatable module
- global, defined by
:global function/variable definition - global, referenced by
:global function/variable references - local, refined and reference exclusively by
:static global/local variable, static global function - non-static local variables are not symbols
- C++ 中类成员函数属于?应当为全局函数,类作为语言特性,其本质应当是封装了不是成员函数而是全局函数的“成员函数”调用
每个 symbol 在 .symtab 中都有一个 entry。.symtab 实体由 assembler 构造,由 compiler 指导构建
- section 部分可以为 pseudosection:ABS,COMMON(uninit global var,.bss for un,in traditional C),UNDEF。
symbol resolution:
- 将 reference 对应到 definition 的过程
- local symbol, 简单,可以由编译器、汇编器完成
- global symbol reference,生成 linker symbol table entry,由 linker 处理
解决 duplicated symbol names:
- 本身就是语言层面的不同实体:mangling for function, static local variables。由编译器预先处理,并将 global 部分交给 linker 继续处理
- 语言层面含义相同的 global symbol,由 linker 处理:
- 传统 C 的处理手段:区分 strong/weak symbol,uninit global var 属于 weak。不允许 multiple strong;single strong + multiple weak 选 strong;multiple weak arbitrary。可能引起 subtle bugs
- C++:ODR,链接错误!
static library
- 优点:不将标准库合并入编译器,将标准库实现与编译器实现解耦;如果作为单个 relocatable 那么整个库各部分无法解耦(编译耗时)、会被完整包含在 executable 内;如果作为多个 relocatable,那么链接十分麻烦
- 将各 relocatable 完整包含,在链接期仅仅被 ref 的部分所在的 relocatable 会被并入 executable
- 具体操作(以 Linux archive 为例):
gcc --static -l -L. -c
ar
。包括生成 archive relocatables,合并 archive,编译源代码并链接 archive。 - 链接时 linker 利用 static library 处理 unresolved referenes 的机制,特别注意 library 相互依赖的情况
Relocation
relocation
- Relocating sections and symbol definitions:将各 relocatables 的各 section 合并组成 executable 的各 section,为新的 section 和 symbol definition 生成 run-time addr。由链接器执行。
- Relocating symbol references within sections:将各 sections 内的 symbol reference 对应到新生成的 symbol definition addr(基于 symbol resolution 的机制确定对应关系)。由链接器和汇编器共同执行
语言层面的 symbol 在汇编层面中的形式
- 局部变量在生成汇编代码的过程中直接被栈地址/寄存器替代。
- 全局变量/局部静态变量 reference 暂时以 symbol 代替
- 全局/全局静态函数函数调用以 call label 替代
汇编器提供的所有机制中底层实现机制大部分都较为直白,对于 symbol(作为语言层面的一部分 data 和 procedure 底层实现,作为机器码中地址引用的上层抽象)而言,其实现机制为 symbol table + symbol resolution 确定 label reference 和 definition 之间的对应关系,然后在 relocation 中确定 definition 的 run-time 地址后,尝试将一部分可以确定 run-time addr 的 symbol 替换为地址,剩余部分做一些准备工作(relocation entry)交给连接器处理
当汇编器遇到不明确 run-time 地址的 reference 时会在其 section 的 relocation entries 中生成对应 entry,原文件处为留白。
relocation types:
- R_X86_64_PC32:a reference that uses a 32-bit PC-relative address.
- R_X86_64_32:a reference that uses a 32-bit absolute address
链接器对 relocation entry 的处理机制:
Executable Object Files
- ELF header:entry point _start
- segment header table:以 segment 为单位,描述了将 object files 中的一段映射到内存上的对应关系、对齐要求
- sections:no .rel.**, .init(_init)
- section header table
Loader
Dynamic Linking
static library
- 无法避免必要的更新 library 时对原的 executable 的 relink
- 各程序都有一份相同的库被加载到内存上
dynamic library
- 可在 loadtime/runtime 加载并链接。
- loadtime:在 loader 加载完 executable 之后发现包含 dynamic linker 路径(其本身为 .so)的 .interp,于是 load 并 run dynamic linker(尝试解析 executable 中 undef reference,依次 relocate 各 shared library,最后将成功解析的部分替换为实际地址,替换并非直接替换而是通过 GOT/PLT 等实现不修改 executable readonly 的代码部分)然后 pass control to application
- 具体操作:
gcc -shared -fpic
构建动态库;loadtime 链接动态库;runtime 链接动态库,实现更高的灵活性:tranditional C dlfcn.hdlopen
,dlsym
,dlclose
,dlerror
,gcc -rdynamic -ldl
- 具体操作:
PIC
为了让多 processes 共享同一块 shared library 代码,必须实现 shared library 为 position independent code,即"无需 relocate"的 code
pic data reference
pic function callls